package com.ken.judge.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ken.api.domain.UserExeResult;
import com.ken.api.domain.dto.JudgeSubmitDTO;
import com.ken.api.domain.vo.UserQuestionResultVO;
import com.ken.common.core.constants.Constants;
import com.ken.common.core.constants.JudgeConstants;
import com.ken.common.core.enums.CodeRunStatus;
import com.ken.judge.domain.SandBoxExecuteResult;
import com.ken.judge.domain.UserSubmit;
import com.ken.judge.mapper.UserSubmitMapper;
import com.ken.judge.service.IJudgeService;
import com.ken.judge.service.ISandboxPoolService;
import com.ken.judge.service.ISandboxService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

/**
 * 它属于判题服务的上层逻辑，负责整合沙箱执行结果、比对测试用例、计算分数，并将判题结果保存到数据库中。
 * 更多的是处理业务的逻辑，而不是直接处理沙箱的执行。它依赖于沙箱服务和数据库服务来完成整个判题流程。</p>)
 * <p>
 * 调用沙箱服务执行代码：通过 ISandboxPoolService 或 ISandboxService 执行用户代码。
 * <p>
 * 判题逻辑处理：比对用户代码的输出与预期输出，检查内存和时间限制。
 * <p>
 * 计算分数：根据判题结果计算用户得分。
 * <p>
 * 保存判题结果：将判题结果保存到数据库。
 */

//实现整体判题逻辑，协调各个组件
@Service
@Slf4j
public class JudgeServiceImpl implements IJudgeService {

    @Autowired
    private ISandboxService sandboxService;

    @Autowired
    private ISandboxPoolService sandboxPoolService;

    @Autowired
    private UserSubmitMapper userSubmitMapper;

    @Override
    public UserQuestionResultVO doJudgeJavaCode(JudgeSubmitDTO judgeSubmitDTO) {
        log.info("---- 判题逻辑开始 -------");
        SandBoxExecuteResult sandBoxExecuteResult = sandboxPoolService.exeJavaCode(judgeSubmitDTO.getUserId(), judgeSubmitDTO.getUserCode(), judgeSubmitDTO.getInputList());
        UserQuestionResultVO userQuestionResultVO = new UserQuestionResultVO();
        if (sandBoxExecuteResult != null && CodeRunStatus.SUCCEED.equals(sandBoxExecuteResult.getRunStatus())) {
            //比对直接结果  时间限制  空间限制的比对
            userQuestionResultVO = doJudge(judgeSubmitDTO, sandBoxExecuteResult, userQuestionResultVO);
        } else {
            userQuestionResultVO.setPass(Constants.FALSE);
            if (sandBoxExecuteResult != null) {
                userQuestionResultVO.setExeMessage(sandBoxExecuteResult.getExeMessage());
            } else {
                userQuestionResultVO.setExeMessage(CodeRunStatus.UNKNOWN_FAILED.getMsg());
            }
            userQuestionResultVO.setScore(JudgeConstants.ERROR_SCORE);
        }
        saveUserSubmit(judgeSubmitDTO, userQuestionResultVO);
        log.info("判题逻辑结束，判题结果为： {} ", userQuestionResultVO);
        return userQuestionResultVO;
    }

    private UserQuestionResultVO doJudge(JudgeSubmitDTO judgeSubmitDTO, SandBoxExecuteResult sandBoxExecuteResult, UserQuestionResultVO userQuestionResultVO) {
        List<String> exeOutputList = sandBoxExecuteResult.getOutputList();
        List<String> outputList = judgeSubmitDTO.getOutputList();
        if (outputList.size() != exeOutputList.size()) {
            userQuestionResultVO.setScore(JudgeConstants.ERROR_SCORE);
            userQuestionResultVO.setPass(Constants.FALSE);
            userQuestionResultVO.setExeMessage(CodeRunStatus.NOT_ALL_PASSED.getMsg());
            return userQuestionResultVO;
        }
        List<UserExeResult> userExeResultList = new ArrayList<>();
        int passedCases = resultCompare(judgeSubmitDTO, exeOutputList, outputList, userExeResultList);
        return assembleUserQuestionResultVO(judgeSubmitDTO, sandBoxExecuteResult, userQuestionResultVO, userExeResultList, passedCases);
    }


    /**
     * 评分逻辑（按优先级判断）：
            通过率太低：如果通过率低于最小要求，得0分，设置相应错误消息

            内存超出限制：得分=基础分×通过率×资源限制惩罚系数(0.5)，设置内存超出消息

            时间超出限制：得分=基础分×通过率×资源限制惩罚系数(0.5)，设置时间超出消息

            全部通过：得满分(基础分)，设置完全正确消息

            部分通过：得分=基础分×通过率，设置部分正确消息
     */

    private UserQuestionResultVO assembleUserQuestionResultVO(JudgeSubmitDTO judgeSubmitDTO, SandBoxExecuteResult sandBoxExecuteResult, UserQuestionResultVO userQuestionResultVO, List<UserExeResult> userExeResultList, int passedCases) {
        userQuestionResultVO.setUserExeResultList(userExeResultList);
        int totalCases = userExeResultList.size();
        double passRatio = (double) passedCases / totalCases;
        int baseScore = judgeSubmitDTO.getDifficulty() * JudgeConstants.DEFAULT_SCORE;
        int actualScore;
        boolean resourceLimitExceeded = false;

        if (passRatio < JudgeConstants.MINIMUM_PASS_RATIO) {
            actualScore = JudgeConstants.ERROR_SCORE;
            userQuestionResultVO.setExeMessage(String.format("通过率 %.0f%% 过低", passRatio * 100));
        } else if (sandBoxExecuteResult.getUseMemory() > judgeSubmitDTO.getSpaceLimit()) {
            resourceLimitExceeded = true;
            actualScore = (int)(baseScore * passRatio * JudgeConstants.RESOURCE_LIMIT_PENALTY);
            userQuestionResultVO.setExeMessage("内存超限");
        } else if (sandBoxExecuteResult.getUseTime() > judgeSubmitDTO.getTimeLimit()) {
            resourceLimitExceeded = true;
            actualScore = (int)(baseScore * passRatio * JudgeConstants.RESOURCE_LIMIT_PENALTY);
            userQuestionResultVO.setExeMessage("时间超限");
        } else if (passedCases == totalCases) {
            actualScore = baseScore;
            userQuestionResultVO.setExeMessage("完全正确");
        } else {
            actualScore = (int)(baseScore * passRatio);
            userQuestionResultVO.setExeMessage(String.format("通过率 %.0f%%", passRatio * 100));
        }

        userQuestionResultVO.setScore(actualScore);
        userQuestionResultVO.setPass(passedCases == totalCases && !resourceLimitExceeded ? Constants.TRUE : Constants.FALSE);
        return userQuestionResultVO;
    }

    private int resultCompare(JudgeSubmitDTO judgeSubmitDTO, List<String> exeOutputList, List<String> outputList, List<UserExeResult> userExeResultList) {
        int passedCases = 0;
        for (int index = 0; index < outputList.size(); index++) {
            String output = outputList.get(index);
            String exeOutPut = exeOutputList.get(index);
            String input = judgeSubmitDTO.getInputList().get(index);
            UserExeResult userExeResult = new UserExeResult();
            userExeResult.setInput(input);
            userExeResult.setOutput(output);
            userExeResult.setExeOutput(exeOutPut);
            userExeResultList.add(userExeResult);
            if (output.equals(exeOutPut)) {
                passedCases++;
            } else {
                log.info("测试用例 #{} 未通过 - 输入：{}， 期望输出：{}， 实际输出：{}", 
                    index + 1, input, output, exeOutPut);
            }
        }
        return passedCases;
    }

    private void saveUserSubmit(JudgeSubmitDTO judgeSubmitDTO, UserQuestionResultVO userQuestionResultVO) {
        UserSubmit userSubmit = new UserSubmit();
        BeanUtil.copyProperties(userQuestionResultVO, userSubmit);
        userSubmit.setUserId(judgeSubmitDTO.getUserId());
        userSubmit.setQuestionId(judgeSubmitDTO.getQuestionId());
        userSubmit.setExamId(judgeSubmitDTO.getExamId());
        userSubmit.setProgramType(judgeSubmitDTO.getProgramType());
        userSubmit.setUserCode(judgeSubmitDTO.getUserCode());
        
        // 限制 exe_message 长度，防止数据库字段溢出
        String exeMessage = userQuestionResultVO.getExeMessage();
        if (exeMessage != null && exeMessage.length() > 990) {
            exeMessage = exeMessage.substring(0, 990) + "...";
            userSubmit.setExeMessage(exeMessage);
        }
        
        userSubmit.setCaseJudgeRes(JSON.toJSONString(userQuestionResultVO.getUserExeResultList()));
        userSubmit.setCreateBy(judgeSubmitDTO.getUserId());
        userSubmitMapper.delete(new LambdaQueryWrapper<UserSubmit>().eq(UserSubmit::getUserId, judgeSubmitDTO.getUserId()).eq(UserSubmit::getQuestionId, judgeSubmitDTO.getQuestionId()).isNull(judgeSubmitDTO.getExamId() == null, UserSubmit::getExamId).eq(judgeSubmitDTO.getExamId() != null, UserSubmit::getExamId, judgeSubmitDTO.getExamId()));
        userSubmitMapper.insert(userSubmit);
    }
}
